/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Script interpreter.  See scrcom.cpp for an explanation of the format
	of compiled scripts.

***************************************************************************/
#include "util.h"
ASSERTNAME

RTCLASS(SCEB)
RTCLASS(SCPT)
RTCLASS(STRG)


#ifdef DEBUG
// these strings are for debug only error messages
static STN _stn;
#endif //DEBUG


/***************************************************************************
	Constructor for the script interpreter.
***************************************************************************/
SCEB::SCEB(PRCA prca, PSTRG pstrg)
{
	AssertNilOrPo(prca, 0);
	AssertNilOrPo(pstrg, 0);

	_pgllwStack = pvNil;
	_pglrtvm = pvNil;
	_pscpt = pvNil;
	_fPaused = fFalse;

	_prca = prca;
	if (pvNil != _prca)
		_prca->AddRef();
	_pstrg = pstrg;
	if (pvNil != _pstrg)
		_pstrg->AddRef();

	AssertThis(0);
}


/***************************************************************************
	Destructor for the script interpreter.
***************************************************************************/
SCEB::~SCEB(void)
{
	Free();
	ReleasePpo(&_prca);
	ReleasePpo(&_pstrg);
}


/***************************************************************************
	Free our claim to all this stuff.
***************************************************************************/
void SCEB::Free(void)
{
	AssertThis(0);

	// nuke literal strings left in the global string table
	if (pvNil != _pscpt && pvNil != _pstrg && pvNil != _pscpt->_pgstLiterals &&
			pvNil != _pglrtvm)
		{
		RTVN rtvn;
		long stid;

		rtvn.lu1 = 0;
		for (rtvn.lu2 = _pscpt->_pgstLiterals->IvMac(); rtvn.lu2-- > 0; )
			{
			if (FFindRtvm(_pglrtvm, &rtvn, &stid, pvNil))
				_pstrg->Delete(stid);
			}
		}

	ReleasePpo(&_pgllwStack);
	ReleasePpo(&_pglrtvm);
	ReleasePpo(&_pscpt);
	_fPaused = fFalse;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a SCEB.
***************************************************************************/
void SCEB::AssertValid(ulong grfsceb)
{
	SCEB_PAR::AssertValid(0);
	if (grfsceb & fscebRunnable)
		{
		Assert(pvNil != _pgllwStack, "nil stack");
		Assert(pvNil != _pscpt, "nil script");
		Assert(_ilwMac == _pscpt->_pgllw->IvMac(), 0);
		AssertIn(_ilwCur, 1, _ilwMac + 1);
		Assert(!_fError, 0);
		}
	AssertNilOrPo(_pgllwStack, 0);
	AssertNilOrPo(_pscpt, 0);
	AssertNilOrPo(_pglrtvm, 0);
	AssertNilOrPo(_pstrg, 0);
	AssertNilOrPo(_prca, 0);
}


/***************************************************************************
	Mark memory for the SCEB.
***************************************************************************/
void SCEB::MarkMem(void)
{
	AssertValid(0);
	SCEB_PAR::MarkMem();
	MarkMemObj(_pgllwStack);
	MarkMemObj(_pscpt);
	MarkMemObj(_pglrtvm);
	MarkMemObj(_prca);
}
#endif //DEBUG


/***************************************************************************
	Run the given script.  (prglw, clw) is the list of parameters for the
	script.
***************************************************************************/
bool SCEB::FRunScript(PSCPT pscpt, long *prglw, long clw,
	long *plwReturn, bool *pfPaused)
{
	AssertThis(0);
	return FAttachScript(pscpt, prglw, clw) && FResume(plwReturn, pfPaused);
}


/***************************************************************************
	Attach a script to this SCEB and pause the script.
***************************************************************************/
bool SCEB::FAttachScript(PSCPT pscpt, long *prglw, long clw)
{
	AssertThis(0);
	AssertPo(pscpt, 0);
	AssertIn(clw, 0, kcbMax);
	AssertPvCb(prglw, LwMul(clw, size(long)));
	long lw;
	DVER dver;

	Free();
	_lwReturn = 0;
	_fError = fFalse;

	//create the stack GL
	if (pvNil == (_pgllwStack = GL::PglNew(size(long), 10)))
		goto LFail;
	_pgllwStack->SetMinGrow(10);

	//stake our claim on the code GL.
	_pscpt = pscpt;
	_pscpt->AddRef();

	//check the version
	//get and check the version number
	if ((_ilwMac = _pscpt->_pgllw->IvMac()) < 1)
		{
		Bug("No version info on script");
		goto LFail;
		}
	_pscpt->_pgllw->Get(0, &lw);
	dver.Set(SwHigh(lw), SwLow(lw));
	if (!dver.FReadable(_SwCur(), _SwMin()))
		{
		Bug("Script version doesn't match script interpreter version");
		goto LFail;
		}

	//add the parameters and literal strings
	if (clw > 0)
		_AddParameters(prglw, clw);
	if (pvNil != _pscpt->_pgstLiterals && !_fError)
		_AddStrings(_pscpt->_pgstLiterals);

	if (_fError)
		{
LFail:
		Free();
		return fFalse;
		}

	//set the pc and claim we're paused
	_ilwCur = 1;
	_fPaused = fTrue;
	AssertThis(fscebRunnable);
	return fTrue;
}


/***************************************************************************
	Resume a paused script.
***************************************************************************/
bool SCEB::FResume(long *plwReturn, bool *pfPaused)
{
	AssertThis(fscebRunnable);
	AssertNilOrVarMem(plwReturn);
	AssertNilOrVarMem(pfPaused);
	RTVN rtvn;
	long ilw, clwPush;
	long lw;
	long op;

	TrashVar(plwReturn);
	TrashVar(pfPaused);
	if (!_fPaused || _fError)
		{
		Bug("script not paused");
		goto LFail;
		}

	AssertIn(_ilwCur, 1, _ilwMac + 1);
	for (_fPaused = fFalse; _ilwCur < _ilwMac && !_fError && !_fPaused; )
		{
		lw = *(long *)_pscpt->_pgllw->QvGet(_ilwCur++);
		clwPush = B2Lw(lw);
		if (!FIn(clwPush, 0, _ilwMac - _ilwCur + 1))
			{
			Bug("bad instruction");
			goto LFail;
			}

		ilw = _ilwCur;
		if (opNil != (op = B3Lw(lw)))
			{
			//this instruction acts on a variable
			if (clwPush == 0)
				{
				Bug("bad var instruction");
				goto LFail;
				}
			clwPush--;
			rtvn.lu1 = (ulong)lw & 0x0000FFFF;
			rtvn.lu2 = *(ulong *)_pscpt->_pgllw->QvGet(_ilwCur++);
			ilw = _ilwCur;
			if (!_FExecVarOp(op, &rtvn))
				goto LFail;
			}
		else if (opNil != (op = SuLow(lw)))
			{
			//normal opcode
			if (!_FExecOp(op))
				goto LFail;
			}

		//push the stack stuff (if we didn't do a jump)
		if (clwPush > 0 && ilw == _ilwCur)
			{
			ilw = _pgllwStack->IvMac();
			if (!_pgllwStack->FSetIvMac(ilw + clwPush))
				{
LFail:
				_Error(fFalse);
				break;
				}
			CopyPb(_pscpt->_pgllw->QvGet(_ilwCur), _pgllwStack->QvGet(ilw),
				LwMul(clwPush, size(long)));
			_ilwCur += clwPush;
			}
		}

	if (_ilwCur >= _ilwMac || _fError)
		_fPaused = fFalse;
	if (!_fPaused)
		Free();
	if (!_fError && pvNil != plwReturn)
		*plwReturn = _lwReturn;
	if (pvNil != pfPaused)
		*pfPaused = _fPaused;

	AssertThis(0);
	return !_fError;
}


/***************************************************************************
	Put the parameters in the local variable list.
***************************************************************************/
void SCEB::_AddParameters(long *prglw, long clw)
{
	AssertThis(0);
	AssertIn(clw, 1, kcbMax);
	AssertPvCb(prglw, LwMul(clw, size(long)));
	STN stn;
	long ilw;
	RTVN rtvn;

	//put the parameters in the local variable gl
	stn = PszLit("_cparm");
	rtvn.SetFromStn(&stn);
	_AssignVar(&_pglrtvm, &rtvn, clw);
	stn = PszLit("_parm");
	rtvn.SetFromStn(&stn);
	for (ilw = 0; ilw < clw; ilw++)
		{
		rtvn.lu1 = LwHighLow(SwLow(ilw), SwLow(rtvn.lu1));
		_AssignVar(&_pglrtvm, &rtvn, prglw[ilw]);
		}
}


/***************************************************************************
	Put the literal strings into the registry.  And assign the string id's
	to the internal string variables.
***************************************************************************/
void SCEB::_AddStrings(PGST pgst)
{
	AssertThis(0);
	AssertPo(pgst, 0);
	RTVN rtvn;
	long stid;
	STN stn;

	if (pvNil == _pstrg)
		{
		_Error(fFalse);
		return;
		}

	rtvn.lu1 = 0;
	for (rtvn.lu2 = 0; (long)rtvn.lu2 < pgst->IvMac(); rtvn.lu2++)
		{
		pgst->GetStn(rtvn.lu2, &stn);
		if (!_pstrg->FAdd(&stid, &stn))
			{
			_Error(fFalse);
			break;
			}
		_AssignVar(&_pglrtvm, &rtvn, stid);
		if (_fError)
			{
			_pstrg->Delete(stid);
			break;
			}
		}
}


/***************************************************************************
	Return the current version number of the script compiler.
***************************************************************************/
short SCEB::_SwCur(void)
{
	AssertBaseThis(0);
	return kswCurSccb;
}


/***************************************************************************
	Return the min version number of the script compiler.  Read can read
	scripts back to this version.
***************************************************************************/
short SCEB::_SwMin(void)
{
	AssertBaseThis(0);
	return kswMinSccb;
}


/***************************************************************************
	Execute an instruction that has a variable as an argument.
***************************************************************************/
bool SCEB::_FExecVarOp(long op, RTVN *prtvn)
{
	AssertThis(0);
	AssertVarMem(prtvn);
	long lw;

	if (FIn(op, kopMinArray, kopLimArray))
		{
		// an array access, munge the rtvn
		lw = _LwPop();
		if (_fError)
			return fFalse;
		prtvn->lu1 = LwHighLow(SwLow(lw), SwLow(prtvn->lu1));
		op += kopPushLocVar - kopPushLocArray;
		}

	switch (op)
		{
	case kopPushLocVar:
		_PushVar(_pglrtvm, prtvn);
		break;
	case kopPopLocVar:
		_AssignVar(&_pglrtvm, prtvn, _LwPop());
		break;
	case kopPushThisVar:
		_PushVar(_PglrtvmThis(), prtvn);
		break;
	case kopPopThisVar:
		_AssignVar(_PpglrtvmThis(), prtvn, _LwPop());
		break;
	case kopPushGlobalVar:
		_PushVar(_PglrtvmGlobal(), prtvn);
		break;
	case kopPopGlobalVar:
		_AssignVar(_PpglrtvmGlobal(), prtvn, _LwPop());
		break;
	case kopPushRemoteVar:
		lw = _LwPop();
		if (!_fError)
			_PushVar(_PglrtvmRemote(lw), prtvn);
		break;
	case kopPopRemoteVar:
		lw = _LwPop();
		if (!_fError)
			_AssignVar(_PpglrtvmRemote(lw), prtvn, _LwPop());
		break;
	default:
		_Error(fTrue);
		break;
		}
	return !_fError;
}


/***************************************************************************
	Execute an instruction.
***************************************************************************/
bool SCEB::_FExecOp(long op)
{
	AssertThis(0);
	double dou;
	long lw1, lw2, lw3;

	// OP's that don't have any arguments
	switch (op)
		{
	case kopExit:
		//jump to the end
		_ilwCur = _ilwMac;
		return fTrue;
	case kopNextCard:
		_Push(vsflUtil.LwNext(0));
		return fTrue;
	case kopPause:
		_fPaused = fTrue;
		return fTrue;
		}

	// OP's that have at least one argument
	lw1 = _LwPop();
	switch (op)
		{
	case kopAdd:
		_Push(_LwPop() + lw1);
		break;
	case kopSub:
		_Push(_LwPop() - lw1);
		break;
	case kopMul:
		_Push(_LwPop() * lw1);
		break;
	case kopDiv:
		if (lw1 == 0)
			_Error(fTrue);
		else
			_Push(_LwPop() / lw1);
		break;
	case kopMod:
		if (lw1 == 0)
			_Error(fTrue);
		else
			_Push(_LwPop() % lw1);
		break;
	case kopNeg:
		_Push(-lw1);
		break;
	case kopInc:
		_Push(lw1 + 1);
		break;
	case kopDec:
		_Push(lw1 - 1);
		break;
	case kopShr:
		_Push((ulong)_LwPop() >> lw1);
		break;
	case kopShl:
		_Push((ulong)_LwPop() << lw1);
		break;
	case kopBOr:
		_Push(_LwPop() | lw1);
		break;
	case kopBAnd:
		_Push(_LwPop() & lw1);
		break;
	case kopBXor:
		_Push(_LwPop() ^ lw1);
		break;
	case kopBNot:
		_Push(~lw1);
		break;
	case kopLXor:
		_Push(FPure(_LwPop()) != FPure(lw1));
		break;
	case kopLNot:
		_Push(!lw1);
		break;
	case kopEq:
		_Push(_LwPop() == lw1);
		break;
	case kopNe:
		_Push(_LwPop() != lw1);
		break;
	case kopGt:
		_Push(_LwPop() > lw1);
		break;
	case kopLt:
		_Push(_LwPop() < lw1);
		break;
	case kopGe:
		_Push(_LwPop() >= lw1);
		break;
	case kopLe:
		_Push(_LwPop() <= lw1);
		break;
	case kopAbs:
		_Push(LwAbs(lw1));
		break;
	case kopRnd:
		if (lw1 <= 0)
			_Error(fTrue);
		else
			_Push(vrndUtil.LwNext(lw1));
		break;
	case kopMulDiv:
		lw2 = _LwPop();
		lw3 = _LwPop();
		if (lw3 == 0)
			_Error(fTrue);
		else
			{
			dou = (double)lw1 * lw2 / lw3;
			if (dou < (double)klwMin || dou > (double)klwMax)
				_Error(fTrue);
			else
				_Push((long)dou);
			}
		break;
	case kopDup:
		_Push(lw1);
		_Push(lw1);
		break;
	case kopPop:
		break;
	case kopSwap:
		lw2 = _LwPop();
		_Push(lw1);
		_Push(lw2);
		break;
	case kopRot:
		lw2 = _LwPop();
		_Rotate(lw2, lw1);
		break;
	case kopRev:
		_Reverse(lw1);
		break;
	case kopDupList:
		_DupList(lw1);
		break;
	case kopPopList:
		_PopList(lw1);
		break;
	case kopRndList:
		_RndList(lw1);
		break;
	case kopSelect:
		_Select(lw1, _LwPop());
		break;
	case kopGoEq:
		lw1 = (_LwPop() == lw1);
		goto LGoNz;
	case kopGoNe:
		lw1 = (_LwPop() != lw1);
		goto LGoNz;
	case kopGoGt:
		lw1 = (_LwPop() > lw1);
		goto LGoNz;
	case kopGoLt:
		lw1 = (_LwPop() < lw1);
		goto LGoNz;
	case kopGoGe:
		lw1 = (_LwPop() >= lw1);
		goto LGoNz;
	case kopGoLe:
		lw1 = (_LwPop() <= lw1);
		goto LGoNz;
	case kopGoZ:
		lw1 = !lw1;
		//fall through
	case kopGoNz:
LGoNz:
		lw2 = _LwPop();
		//labels should have their high byte equal to kbLabel
		if (B3Lw(lw2) != kbLabel || (lw2 &= 0x00FFFFFF) > _ilwMac)
			_Error(fTrue);
		else if (lw1 != 0)
			{
			//perform the goto
			_ilwCur = lw2;
			}
		break;
	case kopGo:
		//labels should have their high byte equal to kbLabel
		if (B3Lw(lw1) != kbLabel || (lw1 &= 0x00FFFFFF) > _ilwMac)
			_Error(fTrue);
		else
			{
			//perform the goto
			_ilwCur = lw1;
			}
		break;
	case kopReturn:
		//jump to the end
		_ilwCur = _ilwMac;
		//fall through
	case kopSetReturn:
		_lwReturn = lw1;
		break;
	case kopShuffle:
		if (lw1 <= 0)
			_Error(fTrue);
		else
			vsflUtil.Shuffle(lw1);
		break;
	case kopShuffleList:
		if (lw1 <= 0)
			_Error(fTrue);
		else
			{
			long *prglw;

			_pgllwStack->Lock();
			prglw = _QlwGet(lw1);
			if (pvNil != prglw)
				vsflUtil.ShuffleRglw(lw1, prglw);
			_pgllwStack->Unlock();
			}
		break;
	case kopMatch:
		_Match(lw1);
		break;
	case kopCopyStr:
		_CopySubStr(lw1, 0, kcchMaxStn, _LwPop());
		break;
	case kopMoveStr:
		if (pvNil == _pstrg)
			_Error(fTrue);
		else if (_pstrg->FMove(lw1, lw2 = _LwPop()))
			_Push(lw2);
		else
			_Push(stidNil);
		break;
	case kopNukeStr:
		if (pvNil == _pstrg)
			_Error(fTrue);
		else
			_pstrg->Delete(lw1);
		break;
	case kopMergeStrs:
		_MergeStrings(lw1, _LwPop());
		break;
	case kopScaleTime:
		vpusac->Scale(lw1);
		break;
	case kopNumToStr:
		_NumToStr(lw1, _LwPop());
		break;
	case kopStrToNum:
		lw2 = _LwPop();
		_StrToNum(lw1, lw2, _LwPop());
		break;
	case kopConcatStrs:
		lw2 = _LwPop();
		_ConcatStrs(lw1, lw2, _LwPop());
		break;
	case kopLenStr:
		_LenStr(lw1);
		break;
	case kopCopySubStr:
		lw2 = _LwPop();
		lw3 = _LwPop();
		_CopySubStr(lw1, lw2, lw3, _LwPop());
		break;

	default:
		_Error(fTrue);
		break;
		}

	return !_fError;
}


/***************************************************************************
	Pop a long off the stack.
***************************************************************************/
long SCEB::_LwPop(void)
{
	long lw, ilw;

	if (_fError)
		return 0;

	//this is faster than just doing FPop
	if ((ilw = _pgllwStack->IvMac()) == 0)
		{
		_Error(fTrue);
		return 0;
		}
	lw = *(long *)_pgllwStack->QvGet(--ilw);
	AssertDo(_pgllwStack->FSetIvMac(ilw), 0);
	return lw;
}


/***************************************************************************
	Get a pointer to the element that is clw elements down from the top.
***************************************************************************/
long *SCEB::_QlwGet(long clw)
{
	long ilwMac;

	if (_fError)
		return pvNil;
	ilwMac = _pgllwStack->IvMac();
	if (!FIn(clw, 1, ilwMac + 1))
		{
		_Error(fTrue);
		return pvNil;
		}
	return (long *)_pgllwStack->QvGet(ilwMac - clw);
}


/***************************************************************************
	Register an error.
***************************************************************************/
void SCEB::_Error(bool fAssert)
{
	AssertThis(0);
	if (!_fError)
		{
		Assert(!fAssert, "Runtime error in script");
		Debug(_WarnSz(PszLit("Runtime error")));
		_fError = fTrue;
		}
}


#ifdef DEBUG
/***************************************************************************
	Emits a warning with the given format string and optional parameters.
***************************************************************************/
void SCEB::_WarnSz(PSZ psz, ...)
{
	AssertThis(0);
	AssertSz(psz);
	STN stn1, stn2;
	SZS szs;

	stn1.FFormatRgch(psz, CchSz(psz), (ulong *)(&psz + 1));
	stn2.FFormatSz(PszLit("Script ('%f', 0x%x, %d): %s"),
			_pscpt->Ctg(), _pscpt->Cno(), _ilwCur, &stn1);
	stn2.GetSzs(szs);
	Warn(szs);
}
#endif //DEBUG


/***************************************************************************
	Rotate clwTot entries on the stack left by clwShift positions.
***************************************************************************/
void SCEB::_Rotate(long clwTot, long clwShift)
{
	AssertThis(0);
	long *qlw;

	if (clwTot == 0 || clwTot == 1)
		return;

	qlw = _QlwGet(clwTot);
	if (qlw != pvNil)
		{
		clwShift %= clwTot;
		if (clwShift < 0)
			clwShift += clwTot;
		AssertIn(clwShift, 0, clwTot);
		if (clwShift != 0)
			{
			SwapBlocks(qlw, LwMul(clwShift, size(long)),
				LwMul(clwTot - clwShift, size(long)));
			}
		}
}


/***************************************************************************
	Reverse clw entries on the stack.
***************************************************************************/
void SCEB::_Reverse(long clw)
{
	AssertThis(0);
	long *qlw, *qlw2;
	long lw;

	if (clw == 0 || clw == 1)
		return;

	qlw = _QlwGet(clw);
	if (qlw != pvNil)
		{
		for (qlw2 = qlw + clw - 1; qlw2 > qlw; )
			{
			lw = *qlw;
			*qlw++ = *qlw2;
			*qlw2-- = lw;
			}
		}
}


/***************************************************************************
	Duplicate clw entries on the stack.
***************************************************************************/
void SCEB::_DupList(long clw)
{
	AssertThis(0);
	long *qlw;

	if (clw == 0)
		return;

	//_QlwGet checks for bad values of clw
	if (_QlwGet(clw) == pvNil)
		return;

	if (!_pgllwStack->FSetIvMac(_pgllwStack->IvMac() + clw))
		_Error(fFalse);
	else
		{
		qlw = _QlwGet(clw * 2);
		Assert(qlw != pvNil, "why did _QlwGet fail?");
		CopyPb(qlw, qlw + clw, LwMul(clw, size(long)));
		}
}


/***************************************************************************
	Removes clw entries from the stack.
***************************************************************************/
void SCEB::_PopList(long clw)
{
	AssertThis(0);
	long ilwMac;

	if (clw == 0 || _fError)
		return;

	ilwMac = _pgllwStack->IvMac();
	if (!FIn(clw, 1, ilwMac + 1))
		_Error(fTrue);
	else
		AssertDo(_pgllwStack->FSetIvMac(ilwMac - clw), "why fail?");
}


/***************************************************************************
	Select the ilw'th entry from the top clw entries.  ilw is indexed from
	the top entry in and is zero based.
***************************************************************************/
void SCEB::_Select(long clw, long ilw)
{
	AssertThis(0);
	long *qlw;

	if (pvNil == (qlw = _QlwGet(clw)))
		return;

	if (!FIn(ilw, 0, clw))
		_Error(fTrue);
	else if (clw > 1)
		{
		qlw[0] = qlw[clw - 1 - ilw];
		_PopList(clw - 1);
		}
}


/***************************************************************************
	The top value is the key, the next is the default return, then come
	clw pairs of test values and return values.  If the key matches a
	test value, push the correspongind return value.  Otherwise, push
	the default return value.
***************************************************************************/
void SCEB::_Match(long clw)
{
	AssertThis(0);
	long *qrglw;
	long lwKey, lwPush, ilwTest;

	lwKey = _LwPop();
	lwPush = _LwPop();
	if (pvNil == (qrglw = _QlwGet(2 * clw)))
		return;

	// start at high memory (top of the stack).
	for (ilwTest = 2 * clw - 1; ilwTest > 0; ilwTest -= 2)
		{
		if (qrglw[ilwTest] == lwKey)
			{
			lwPush = qrglw[ilwTest - 1];
			break;
			}
		}
	qrglw[0] = lwPush;
	_PopList(2 * clw - 1);
}


/***************************************************************************
	Generates a random entry from a list of numbers on the stack.
***************************************************************************/
void SCEB::_RndList(long clw)
{
	AssertThis(0);

	if (clw <= 0)
		_Error(fTrue);
	else
		_Select(clw, vrndUtil.LwNext(clw));
}


/***************************************************************************
	Copy the string from stidSrc to stidDst.
***************************************************************************/
void SCEB::_CopySubStr(long stidSrc, long ichMin, long cch, long stidDst)
{
	AssertThis(0);
	STN stn;

	if (pvNil == _pstrg)
		_Error(fTrue);

	if (_fError)
		return;

	if (!_pstrg->FGet(stidSrc, &stn))
		Debug(_WarnSz(PszLit("Source string doesn't exist (stid = %d)"), stidSrc));

	if (ichMin > 0)
		stn.Delete(0, ichMin);
	if (cch < stn.Cch())
		stn.Delete(cch);
	if (!_pstrg->FPut(stidDst, &stn))
		{
		Debug(_WarnSz(PszLit("Setting dst string failed")));
		_Push(stidNil);
		}
	else
		_Push(stidDst);
}


/***************************************************************************
	Concatenate two strings and put the result in a third. Push the id
	of the destination.
***************************************************************************/
void SCEB::_ConcatStrs(long stidSrc1, long stidSrc2, long stidDst)
{
	AssertThis(0);
	STN stn1, stn2;

	if (pvNil == _pstrg)
		_Error(fTrue);

	if (_fError)
		return;

	if (!_pstrg->FGet(stidSrc1, &stn1))
		{
		Debug(_WarnSz(PszLit("Source string 1 doesn't exist (stid = %d)"),
			stidSrc1));
		}

	if (!_pstrg->FGet(stidSrc2, &stn2))
		{
		Debug(_WarnSz(PszLit("Source string 2 doesn't exist (stid = %d)"),
			stidSrc2));
		}

	stn1.FAppendStn(&stn2);
	if (!_pstrg->FPut(stidDst, &stn1))
		{
		Debug(_WarnSz(PszLit("Setting dst string failed")));
		_Push(stidNil);
		}
	else
		_Push(stidDst);
}


/***************************************************************************
	Push the length of the given string.
***************************************************************************/
void SCEB::_LenStr(long stid)
{
	AssertThis(0);
	STN stn;

	if (pvNil == _pstrg)
		_Error(fTrue);

	if (!_pstrg->FGet(stid, &stn))
		{
		Debug(_WarnSz(PszLit("Source string doesn't exist (stid = %d)"),
			stid));
		}

	_Push(stn.Cch());
}


/***************************************************************************
	CRF reader function to read a string registry string table.
***************************************************************************/
bool _FReadStringReg(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
	PBACO *ppbaco, long *pcb)
{
	AssertPo(pcrf, 0);
	AssertPo(pblck, fblckReadable);
	AssertNilOrVarMem(ppbaco);
	AssertVarMem(pcb);
	PGST pgst;
	PCABO pcabo;
	short bo;

	*pcb = pblck->Cb(fTrue);
	if (pvNil == ppbaco)
		return fTrue;

	if (!pblck->FUnpackData())
		return fFalse;
	*pcb = pblck->Cb();

	if (pvNil == (pgst = GST::PgstRead(pblck, &bo)) ||
			pgst->CbExtra() != size(long))
		{
		goto LFail;
		}

	if (kboOther == bo)
		{
		long istn, stid;

		for (istn = pgst->IvMac(); istn-- > 0; )
			{
			pgst->GetExtra(istn, &stid);
			SwapBytesRglw(&stid, 1);
			pgst->PutExtra(istn, &stid);
			}
		}

	if (pvNil == (pcabo = NewObj CABO(pgst)))
		{
LFail:
		ReleasePpo(&pgst);
		TrashVar(pcb);
		TrashVar(ppbaco);
		return fFalse;
		}

	*ppbaco = pcabo;
	return fTrue;
}


/***************************************************************************
	Merge a string table into the string registry.
***************************************************************************/
void SCEB::_MergeStrings(CNO cno, RSC rsc)
{
	AssertThis(0);
	PCABO pcabo;
	PGST pgst;
	long istn, stid;
	STN stn;
	bool fFail;

	if (_fError)
		return;

	if (pvNil == _pstrg)
		{
		Bug("no string registry to put the string in");
		_Error(fFalse);
		return;
		}

	if (pvNil == _prca)
		{
		Bug("no rca to read string table from");
		_Error(fFalse);
		return;
		}

	if (pvNil == (pcabo = (PCABO)_prca->PbacoFetch(kctgStringReg, cno,
			&_FReadStringReg, pvNil, rsc)))
		{
		Debug(_WarnSz(PszLit("Reading string table failed (cno = 0x%x)"),
			cno));
		return;
		}

	Assert(pcabo->po->FIs(kclsGST), 0);
	pgst = (PGST)pcabo->po;
	Assert(pgst->CbExtra() == size(long), 0);

	fFail = fFalse;
	for (istn = pgst->IvMac(); istn-- > 0; )
		{
		pgst->GetStn(istn, &stn);
		pgst->GetExtra(istn, &stid);
		fFail |= !_pstrg->FPut(stid, &stn);
		}

#ifdef DEBUG
	if (fFail)
		_WarnSz(PszLit("Merging string table failed"));
#endif //DEBUG

	pcabo->SetCrep(crepTossFirst);
	ReleasePpo(&pcabo);
}


/***************************************************************************
	Convert a number to a string and add the string to the registry.
***************************************************************************/
void SCEB::_NumToStr(long lw, long stid)
{
	AssertThis(0);
	STN stn;

	if (pvNil == _pstrg)
		_Error(fTrue);

	if (_fError)
		return;

	stn.FFormatSz(PszLit("%d"), lw);
	if (!_pstrg->FPut(stid, &stn))
		{
		Debug(_WarnSz(PszLit("Putting string in registry failed")));
		stid = stidNil;
		}
	_Push(stid);
}


/***************************************************************************
	Convert a string to a number and push the result. If the string is
	empty, push lwEmpty; if there is an error, push lwError.
***************************************************************************/
void SCEB::_StrToNum(long stid, long lwEmpty, long lwError)
{
	AssertThis(0);
	STN stn;
	long lw;

	if (pvNil == _pstrg)
		_Error(fTrue);

	if (_fError)
		return;

	_pstrg->FGet(stid, &stn);
	if (0 == stn.Cch())
		lw = lwEmpty;
	else if (!stn.FGetLw(&lw, 0))
		lw = lwError;
	_Push(lw);
}


/***************************************************************************
	Push the value of a variable onto the runtime stack.
***************************************************************************/
void SCEB::_PushVar(PGL pglrtvm, RTVN *prtvn)
{
	AssertThis(0);
	AssertVarMem(prtvn);
	AssertNilOrPo(pglrtvm, 0);
	long lw;

	if (_fError)
		return;

	if (pvNil == pglrtvm || !FFindRtvm(pglrtvm, prtvn, &lw, pvNil))
		{
#ifdef DEBUG
		prtvn->GetStn(&_stn);
		_WarnSz(PszLit("Pushing uninitialized script variable: %s"),
			&_stn);
#endif //DEBUG
		_Push(0);
		}
	else
		_Push(lw);
}


/***************************************************************************
	Pop the top value off the runtime stack into a variable.
***************************************************************************/
void SCEB::_AssignVar(PGL *ppglrtvm, RTVN *prtvn, long lw)
{
	AssertThis(0);
	AssertVarMem(prtvn);
	AssertNilOrVarMem(ppglrtvm);

	if (_fError)
		return;

	if (pvNil == ppglrtvm)
		{
		_Error(fTrue);
		return;
		}

	if (!FAssignRtvm(ppglrtvm, prtvn, lw))
		_Error(fFalse);
}


/***************************************************************************
	Get the variable map for "this" object.
***************************************************************************/
PGL SCEB::_PglrtvmThis(void)
{
	PGL *ppgl = _PpglrtvmThis();
	if (pvNil == ppgl)
		return pvNil;
	return *ppgl;
}


/***************************************************************************
	Get the adress of the variable map master pointer for "this" object
	(so we can create the variable map if need be).
***************************************************************************/
PGL *SCEB::_PpglrtvmThis(void)
{
	return pvNil;
}


/***************************************************************************
	Get the variable map for "global" variables.
***************************************************************************/
PGL SCEB::_PglrtvmGlobal(void)
{
	PGL *ppgl = _PpglrtvmGlobal();
	if (pvNil == ppgl)
		return pvNil;
	return *ppgl;
}


/***************************************************************************
	Get the adress of the variable map master pointer for "global" variables
	(so we can create the variable map if need be).
***************************************************************************/
PGL *SCEB::_PpglrtvmGlobal(void)
{
	return pvNil;
}


/***************************************************************************
	Get the variable map for a remote object.
***************************************************************************/
PGL SCEB::_PglrtvmRemote(long lw)
{
	PGL *ppgl = _PpglrtvmRemote(lw);
	if (pvNil == ppgl)
		return pvNil;
	return *ppgl;
}


/***************************************************************************
	Get the adress of the variable map master pointer for a remote object
	(so we can create the variable map if need be).
***************************************************************************/
PGL *SCEB::_PpglrtvmRemote(long lw)
{
	return pvNil;
}


/***************************************************************************
	Find a RTVM in the pglrtvm.  Assumes the pglrtvm is sorted by rtvn.
	If the RTVN is not in the GL, sets *pirtvm to where it would be if
	it were.
***************************************************************************/
bool FFindRtvm(PGL pglrtvm, RTVN *prtvn, long *plw, long *pirtvm)
{
	AssertPo(pglrtvm, 0);
	AssertVarMem(prtvn);
	AssertNilOrVarMem(plw);
	AssertNilOrVarMem(pirtvm);
	RTVM *qrgrtvm, *qrtvm;
	long irtvm, irtvmMin, irtvmLim;

	qrgrtvm = (RTVM *)pglrtvm->QvGet(0);
	for (irtvmMin = 0, irtvmLim = pglrtvm->IvMac(); irtvmMin < irtvmLim; )
		{
		irtvm = (irtvmMin + irtvmLim) / 2;
		qrtvm = qrgrtvm + irtvm;
		if (qrtvm->rtvn.lu1 < prtvn->lu1)
			irtvmMin = irtvm + 1;
		else if (qrtvm->rtvn.lu1 > prtvn->lu1)
			irtvmLim = irtvm;
		else if (qrtvm->rtvn.lu2 < prtvn->lu2)
			irtvmMin = irtvm + 1;
		else if (qrtvm->rtvn.lu2 > prtvn->lu2)
			irtvmLim = irtvm;
		else
			{
			//we found it
			if (pvNil != plw)
				*plw = qrtvm->lwValue;
			if (pvNil != pirtvm)
				*pirtvm = irtvm;
			return fTrue;
			}
		}
	TrashVar(plw);
	if (pvNil != pirtvm)
		*pirtvm = irtvmMin;
	return fFalse;
}


/***************************************************************************
	Put the given value into a runtime variable.
***************************************************************************/
bool FAssignRtvm(PGL *ppglrtvm, RTVN *prtvn, long lw)
{
	AssertVarMem(ppglrtvm);
	AssertNilOrPo(*ppglrtvm, 0);
	AssertVarMem(prtvn);
	RTVM rtvm;
	long irtvm;

	rtvm.lwValue = lw;
	rtvm.rtvn = *prtvn;
	if (pvNil == *ppglrtvm)
		{
		if (pvNil == (*ppglrtvm = GL::PglNew(size(RTVM))))
			return fFalse;
		(*ppglrtvm)->SetMinGrow(10);
		irtvm = 0;
		}
	else if (FFindRtvm(*ppglrtvm, prtvn, pvNil, &irtvm))
		{
		(*ppglrtvm)->Put(irtvm, &rtvm);
		return fTrue;
		}

	return (*ppglrtvm)->FInsert(irtvm, &rtvm);
}



/***************************************************************************
	A chunky resource reader to read a script.
***************************************************************************/
bool SCPT::FReadScript(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
	PBACO *ppbaco, long *pcb)
{
	AssertPo(pcrf, 0);
	AssertPo(pblck, fblckReadable);
	AssertNilOrVarMem(ppbaco);

	*pcb = pblck->Cb(fTrue);
	if (pvNil == ppbaco)
		return fTrue;

	*ppbaco = PscptRead(pcrf->Pcfl(), ctg, cno);
	return pvNil != *ppbaco;
}


/***************************************************************************
	Static method to read a script.
***************************************************************************/
PSCPT SCPT::PscptRead(PCFL pcfl, CTG ctg, CNO cno)
{
	AssertPo(pcfl, 0);
	short bo;
	KID kid;
	BLCK blck;
	PSCPT pscpt = pvNil;
	PGL pgllw = pvNil;
	PGST pgst = pvNil;

	if (!pcfl->FFind(ctg, cno, &blck))
		goto LFail;

	if (pvNil == (pgllw = GL::PglRead(&blck, &bo)) ||
			pgllw->CbEntry() != size(long))
		{
		goto LFail;
		}
	if (pcfl->FGetKidChidCtg(ctg, cno, 0, kctgScriptStrs, &kid))
		{
		if (!pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck) ||
				pvNil == (pgst = GST::PgstRead(&blck)))
			{
			goto LFail;
			}
		}
	if (pvNil == (pscpt = NewObj SCPT))
		{
LFail:
		ReleasePpo(&pgllw);
		ReleasePpo(&pgst);
		return pvNil;
		}

	if (kboOther == bo)
		SwapBytesRglw(pgllw->QvGet(0), pgllw->IvMac());
	pscpt->_pgllw = pgllw;
	pscpt->_pgstLiterals = pgst;
	return pscpt;
}


/***************************************************************************
	Destructor for a script.
***************************************************************************/
SCPT::~SCPT(void)
{
	AssertBaseThis(0);
	ReleasePpo(&_pgllw);
	ReleasePpo(&_pgstLiterals);
}


/***************************************************************************
	Save the script to the given chunky file.
***************************************************************************/
bool SCPT::FSaveToChunk(PCFL pcfl, CTG ctg, CNO cno, bool fPack)
{
	AssertThis(0);
	AssertPo(pcfl, 0);
	BLCK blck;
	CNO cnoT, cnoStrs;
	long cb;

	// write the script chunk
	cb = _pgllw->CbOnFile();
	if (!blck.FSetTemp(cb) || !_pgllw->FWrite(&blck))
		return fFalse;
	if (fPack)
		blck.FPackData();

	if (!pcfl->FFind(ctg, cno))
		{
		// chunk doesn't exist, just write it
		if (!pcfl->FPutBlck(&blck, ctg, cnoT = cno))
			return fFalse;
		}
	else
		{
		// chunk already exists - add a new temporary one
		if (!pcfl->FAddBlck(&blck, ctg, &cnoT))
			return fFalse;
		}

	// write the string table if there is one.  The cno for this is allocated
	// via FAdd.
	if (pvNil != _pgstLiterals)
		{
		cb = _pgstLiterals->CbOnFile();
		if (!blck.FSetTemp(cb) || !_pgstLiterals->FWrite(&blck))
			goto LFail;
		if (fPack)
			blck.FPackData();

		if (!pcfl->FAddBlck(&blck, kctgScriptStrs, &cnoStrs))
			goto LFail;
		if (!pcfl->FAdoptChild(ctg, cnoT, kctgScriptStrs, cnoStrs))
			{
			pcfl->Delete(kctgScriptStrs, cnoStrs);
LFail:
			pcfl->Delete(ctg, cnoT);
			return fFalse;
			}
		}

	// swap the data and children of the temporary chunk and the destination
	if (cno != cnoT)
		{
		pcfl->SwapData(ctg, cno, ctg, cnoT);
		pcfl->SwapChildren(ctg, cno, ctg, cnoT);
		pcfl->Delete(ctg, cnoT);
		}

	return fTrue;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a SCPT.
***************************************************************************/
void SCPT::AssertValid(ulong grf)
{
	SCPT_PAR::AssertValid(0);
	AssertPo(_pgllw, 0);
	AssertNilOrPo(_pgstLiterals, 0);
}


/***************************************************************************
	Mark memory for the SCPT.
***************************************************************************/
void SCPT::MarkMem(void)
{
	AssertValid(0);
	SCPT_PAR::MarkMem();
	MarkMemObj(_pgllw);
	MarkMemObj(_pgstLiterals);
}
#endif //DEBUG


/***************************************************************************
	Constructor for the runtime string registry.
***************************************************************************/
STRG::STRG(void)
{
	_pgst = pvNil;
	AssertThis(0);
}


/***************************************************************************
	Constructor for the runtime string registry.
***************************************************************************/
STRG::~STRG(void)
{
	AssertThis(0);
	ReleasePpo(&_pgst);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a STRG.
***************************************************************************/
void STRG::AssertValid(ulong grf)
{
	STRG_PAR::AssertValid(0);
	AssertNilOrPo(_pgst, 0);
}


/***************************************************************************
	Mark memory for the STRG.
***************************************************************************/
void STRG::MarkMem(void)
{
	AssertValid(0);
	STRG_PAR::MarkMem();
	MarkMemObj(_pgst);
}
#endif //DEBUG


/***************************************************************************
	Put the string in the registry with the given string id.
***************************************************************************/
bool STRG::FPut(long stid, PSTN pstn)
{
	AssertThis(0);
	AssertPo(pstn, 0);
	long istn;

	if (!_FEnsureGst())
		return fFalse;

	if (_FFind(stid, &istn))
		return _pgst->FPutStn(istn, pstn);

	return _pgst->FInsertStn(istn, pstn, &stid);
}


/***************************************************************************
	Get the string with the given string id.  If the string isn't in the
	registry, sets pstn to an empty string and returns false.
***************************************************************************/
bool STRG::FGet(long stid, PSTN pstn)
{
	AssertThis(0);
	AssertPo(pstn, 0);
	long istn;

	if (!_FFind(stid, &istn))
		{
		pstn->SetNil();
		return fFalse;
		}

	_pgst->GetStn(istn, pstn);
	return fTrue;
}


/***************************************************************************
	Add a string to the registry, assigning it an unused id.  The assigned
	id's are not repeated in the near future.  All assigned id's have their
	high bit set.
***************************************************************************/
bool STRG::FAdd(long *pstid, PSTN pstn)
{
	AssertThis(0);
	AssertVarMem(pstid);
	AssertPo(pstn, 0);
	long istn;

	for (;;)
		{
		_stidLast = (_stidLast + 1) | 0x80000000L;
		if (!_FFind(_stidLast, &istn))
			break;
		}

	*pstid = _stidLast;
	return FPut(_stidLast, pstn);
}


/***************************************************************************
	Delete a string from the registry.
***************************************************************************/
void STRG::Delete(long stid)
{
	AssertThis(0);
	long istn;

	if (_FFind(stid, &istn))
		_pgst->Delete(istn);
}


/***************************************************************************
	Change the id of a string from stidSrc to stidDst.  If stidDst already
	exists, it is replaced.  Returns false if the source string doesn't
	exist.  Can't fail if the source does exist.
***************************************************************************/
bool STRG::FMove(long stidSrc, long stidDst)
{
	AssertThis(0);
	long istnSrc, istnDst;

	if (stidSrc == stidDst)
		return _FFind(stidSrc, &istnSrc);

	if (!_FFind(stidDst, &istnSrc))
		return fFalse;

	if (_FFind(stidDst, &istnDst))
		{
		_pgst->Delete(istnDst);
		if (istnSrc > istnDst)
			istnSrc--;
		}

	_pgst->PutExtra(istnSrc, &stidDst);
	_pgst->Move(istnSrc, istnDst);
	return fTrue;
}


/***************************************************************************
	Do a binary search for the string id.  Returns true iff the string is
	in the registry.  In either case, sets *pistn with where the string
	should go.
***************************************************************************/
bool STRG::_FFind(long stid, long *pistn)
{
	AssertThis(0);
	AssertVarMem(pistn);
	long ivMin, ivLim, iv;
	long stidT;

	if (pvNil == _pgst)
		{
		*pistn = 0;
		return fFalse;
		}

	for (ivMin = 0, ivLim = _pgst->IvMac(); ivMin < ivLim; )
		{
		iv = (ivMin + ivLim) / 2;
		_pgst->GetExtra(iv, &stidT);
		if (stidT < stid)
			ivMin = iv + 1;
		else if (stidT > stid)
			ivLim = iv;
		else
			{
			*pistn = iv;
			return fTrue;
			}
		}

	*pistn = ivMin;
	return fFalse;
}


/***************************************************************************
	Make sure the GST exists.
***************************************************************************/
bool STRG::_FEnsureGst(void)
{
	AssertThis(0);

	if (pvNil != _pgst)
		return fTrue;
	_pgst = GST::PgstNew(size(long));
	AssertThis(0);

	return pvNil != _pgst;
}


